#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
'''
@File    :   fncalc.py
@Time    :   2020/11/06
@Author  :   wenke wang
@Version :   1.0
@Desc    :   膳食营养方案计算器
'''

# here put the import lib
import math,os,sys,random,getopt
from decimal import Decimal,getcontext
from copy import deepcopy
from flask import Flask, request, jsonify
from flask_cors import CORS, cross_origin
import logging
from person import Person
from config import *

def init_logging(log_dir=None):
    # if not os.path.exists(log_dir):
    #     os.mkdir(log_dir)

    logger = logging.getLogger(__name__)
    logger.setLevel(level = logging.INFO)

    # fh = logging.FileHandler(os.path.join(log_dir, "fncalc.log"))
    # fh.setLevel(logging.DEBUG)
    # formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    # fh.setFormatter(formatter)
    
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    
    # logger.addHandler(fh)
    logger.addHandler(ch)

    return logger

logger = init_logging()

class FoodNutritionCalculator():
    """
    膳食方案计算器
    """
    def __init__(self, pal_coefficient_path=None, energy_path=None, food_num_path=None):
        if not pal_coefficient_path:
            pal_coefficient_path = os.path.join(sys.path[0], "pal_coefficient.csv")
        if not energy_path:
            energy_path = os.path.join(sys.path[0], "energy_exchange_setting.json")
        if not food_num_path:
            food_num_path = os.path.join(sys.path[0], "food_num_setting.json")
        self.__palcoe_util = PalCoefficientUtil(pal_coefficient_path)
        self.__energy = Energy(energy_path)
        self.__food_num = FoodNumSetting(food_num_path)

    def __calc_tee(self, person, formula=ReeFormula.NORMAL):
        """
        计算TEE
        """
        pal_coe = self.__palcoe_util.get_coe(person.age, person.pal)
        if not pal_coe:
            raise ValueError("could not get PAL's coefficient of this age: %d" % person.age)
        ree = person.classic_ree if formula == ReeFormula.CLASSIC else person.normal_ree
        tee = int(ree * pal_coe)
        return tee

    def __calc_ingredient(self, num, base_content):
        """
        计算食物成分的份数和含量
        """
        num = Decimal(str(num))
        precision1 = Decimal("0.0")
        return {
            "num": str(num),
            "weight": str(Decimal(num * Decimal(base_content["weight"])).quantize(precision1)),
            "protein": str(Decimal(num * Decimal(base_content["protein"])).quantize(precision1)),
            "fat": str(Decimal(num * Decimal(base_content["fat"])).quantize(precision1)),
            "carbohydrates": str(Decimal(num * Decimal(base_content["carbohydrates"])).quantize(precision1))
        }

    def __calc_percent(self, base_percent):
        """
        随机生成谷薯类、鱼禽虾肉、油脂类占剩余份数比例
        """
        cap_per = random.randint(int(base_percent["cereals_and_potatoes"]["low"] * 1000), int(base_percent["cereals_and_potatoes"]["high"] * 1000))
        fps_per = random.randint(int(base_percent["fish_poultry_shrimp"]["low"] * 1000), int(base_percent["fish_poultry_shrimp"]["high"] * 1000))
        gre_per = 1000 - cap_per - fps_per
        precision3 = Decimal("0.000")
        return {
            "cereals_and_potatoes": str(Decimal(str(cap_per / 1000)).quantize(precision3)),
            "fish_poultry_shrimp": str(Decimal(str(fps_per / 1000)).quantize(precision3)),
            "grease": str(Decimal(str(gre_per / 1000)).quantize(precision3))
        }
        
    def __sum_ingredient(self, vars, key):
        """
        求成分之和
        """
        return sum([Decimal(vars[ing][key]) for ing in vars])

    def calc_foodexchange(self, person, ree_formula=ReeFormula.NORMAL):
        """
        按人计算食物交换份数
        """
        # TEE
        tee_kcal = self.__calc_tee(person, formula=ree_formula)
        return self.calc_foodexchange_by_tee(tee_kcal)

    def calc_foodexchange_by_tee(self, tee):
        """
        按TEE计算食物交换份数
        """
        if tee < 0:
            raise ValueError("The TEE was less than ZERO! Please check your parameters.")
        precision1 = Decimal("0.0")
        tee_kcal = tee
        tee_kj = int(tee_kcal / 0.2389)
        base_energy = self.__energy.setting
        # 实际总份数
        total_num = Decimal(Decimal(tee_kcal) / Decimal(base_energy["per_energy"]["kcal"])).quantize(precision1)
        # 基础份数和含量
        wac = base_energy["weight_and_content"]
        # 自变量
        indepvar = {
            "vegetables": self.__calc_ingredient(1.0, wac["vegetables_and_fruits_group"]["vegetables"]),
            "fruits": self.__calc_ingredient(1.0, wac["vegetables_and_fruits_group"]["fruits"]),
            "soybean": self.__calc_ingredient(1.0, wac["meat_and_eggs_group"]["soybean"]),
            "dairyproducts": self.__calc_ingredient(1.6, wac["meat_and_eggs_group"]["dairyproducts"]),
            "eggs": self.__calc_ingredient(1.0, wac["meat_and_eggs_group"]["eggs"]),
            "nuts": self.__calc_ingredient(0.0, wac["grease_group"]["nuts"])
        }
        # 随机比例
        rand_percent = self.__calc_percent(base_energy["rand_percent"])
        # 剩余份数
        remaining_num = total_num - self.__sum_ingredient(indepvar, "num")
        # 谷薯类份数
        cap_num = str(Decimal(Decimal(rand_percent["cereals_and_potatoes"]) * remaining_num).quantize(precision1))
        # 鱼禽虾肉份数
        fps_num = str(Decimal(Decimal(rand_percent["fish_poultry_shrimp"]) * remaining_num).quantize(precision1))
        # 油脂类份数
        gre_num = str(Decimal(Decimal(rand_percent["grease"]) * remaining_num).quantize(precision1))
        # 变量
        calcvar = {
            "cereals_and_potatoes": self.__calc_ingredient(cap_num, wac["cereals_and_potatoes_group"]["cereals_and_potatoes"]),
            "fish_poultry_shrimp": self.__calc_ingredient(fps_num, wac["meat_and_eggs_group"]["fish_poultry_shrimp"]),
            "grease": self.__calc_ingredient(gre_num, wac["grease_group"]["grease"])
        }
        # 实际总重量
        total_weight = self.__sum_ingredient(indepvar, "weight")
        total_weight = str(total_weight + self.__sum_ingredient(calcvar, "weight"))
        # 蛋白质重量
        protein_weight = self.__sum_ingredient(indepvar, "protein")
        protein_weight = str(protein_weight + self.__sum_ingredient(calcvar, "protein"))
        # 脂肪重量
        fat_weight = self.__sum_ingredient(indepvar, "fat")
        fat_weight = str(fat_weight + self.__sum_ingredient(calcvar, "fat"))
        # 碳水化合物重量
        carbohydrates_weight = self.__sum_ingredient(indepvar, "carbohydrates")
        carbohydrates_weight = str(carbohydrates_weight + self.__sum_ingredient(calcvar, "carbohydrates"))
        # 结果
        result = {
            "tee_kcal": tee_kcal,
            "tee_kj": tee_kj,
            "total_num": str(total_num),
            "total_weight": total_weight,
            "protein_weight": protein_weight,
            "fat_weight": fat_weight,
            "carbohydrates_weight": carbohydrates_weight,
            "rand_percent": rand_percent
        }
        result.update(indepvar)
        result.update(calcvar)
        return result

    def calc_foodnum_oneday(self, food_exchange, result_structure="bymeal"):
        """
        计算一日食物份数
        """
        precision1 = Decimal("0.0")
        result = {}
        fcoe_oneday = self.__food_num.setting["food_coefficient_oneday"]
        if result_structure == "byfood":
            for ckey in fcoe_oneday:
                exchange_num = Decimal(food_exchange[ckey]["num"])
                meals = deepcopy(fcoe_oneday[ckey])
                for mkey in meals:
                    fcoe = Decimal(meals[mkey])
                    meals[mkey] = str(Decimal(exchange_num * fcoe).quantize(precision1))
                result[ckey] = meals
        else:
            for m in self.__food_num.setting["meals"]:
                meal = {
                    "name": m["name"],
                    "percent": m["percent"],
                    "food": {},
                    "food_sum": "0"
                }
                food_sum = Decimal("0.0")
                for ckey in fcoe_oneday:
                    exchange_num = Decimal(food_exchange[ckey]["num"])
                    fcoe = Decimal(fcoe_oneday[ckey][m["id"]])
                    food_num = Decimal(exchange_num * fcoe).quantize(precision1)
                    food_sum = food_sum + food_num
                    meal["food"][ckey] = str(food_num)
                    meal["food_sum"] = str(food_sum.quantize(precision1))
                result[m["id"]] = meal

        return result

app = Flask(__name__)
# CORS(app=app, resources=r'/*')

@app.route("/fn/calculate", methods = ["GET", "POST", "OPTIONS"])
@cross_origin(origins="*", send_wildcard=True)
def calculate():
    """
    计算每日食物份数
    """
    gender = request.values.get("gender")
    if not gender or not gender.strip().isdigit() or gender.strip() not in ["1", "2"]:
        return jsonify({ "result_code": 401, "error": "Field['gender'] is invalid. it should be 1(male) or 2(female)." })
    birthday = request.values.get("birthday")
    if not birthday:
        return jsonify({ "result_code": 401, "error": "Field['birthday'] is invalid. it's format should be 'YYYY-MM-DD'." })
    height = request.values.get("height")
    if not height or not height.strip().isdigit():
        return jsonify({ "result_code": 401, "error": "Field['height'] is invalid. it should be a digit and it's unit is 'kg'." })
    weight = request.values.get("weight")
    if not weight or not weight.strip().isdigit():
        return jsonify({ "result_code": 401, "error": "Field['weight'] is invalid. it should be a digit and it's unit is 'cm'." })
    pal = request.values.get("pal")
    if not pal or not pal.strip().isdigit() or pal.strip() not in ["1", "2", "3"]:
        return jsonify({ "result_code": 401, "error": "Field['pal'] is invalid. it should be 1(light) or 2(moderate) or 3(severe)." })

    ree_formula = request.values.get("ree")
    if not ree_formula or not ree_formula.strip().isdigit() or ree_formula.strip() not in ["1", "2"]:
        ree_formula = ReeFormula.NORMAL

    result_structure = request.values.get("result_structure")
    if not result_structure or result_structure.lower() != "byfood" or result_structure.lower() != "bymeal":
        result_structure = "bymeal"
    try:
        person = Person(int(gender), birthday.strip(), height.strip(), weight.strip(), int(pal))
        person.validate()
        food_exchange = app.calculator.calc_foodexchange(person, ree_formula=ree_formula)
        logger.info("食物交换份数：%s" % json.dumps(food_exchange))
        foodnum_oneday = app.calculator.calc_foodnum_oneday(food_exchange, result_structure=result_structure)
        logger.info("一日食物份数：%s" % json.dumps(foodnum_oneday))
        return jsonify({ "result_code": 200, "data": {"food_exchange": food_exchange, "foodnum_oneday": foodnum_oneday} })
    except BaseException as ex:
        logger.error(ex)
        return jsonify({ "result_code": 500, "error": ex.args[0] if len(ex.args) > 0 else "api internal error" })

def get_argv(shortopts, longopts = []):
    """
    get dictionary of option arguments and list of unnamed arguments
    """
    opts, args = getopt.gnu_getopt(sys.argv[1:], shortopts, longopts=longopts)
    opt_dict = dict(opts)
    return opt_dict, args

def dict_get_mkey(_dict, *keys, default=None):
    """
    get value of dictionary by multiple optional keys
    """
    value = None
    for k in keys:
        if k in _dict:
            value = _dict[k]
            break
    
    return value if value else default

if __name__ == "__main__":
    opt_dict, args = get_argv("h:p:", longopts=["host=", "port=", "help"])
    if "--help" in opt_dict:
        print("""用法: python ocr_app.py [-h|--host mod, -p|--port mod]\n
        Options and arguments:\n
        -h|--host mod             : Restful API服务启动地址，默认值：127.0.0.1\n
        -p|--port mod             : Restful API绑定的端口号，默认值：19090\n
        """)
    else:
        host = dict_get_mkey(opt_dict, "-h", "--host", default="127.0.0.1")
        port = int(dict_get_mkey(opt_dict, "-p", "--port", default=19090))
        app.calculator = FoodNutritionCalculator()
        app.run(host=host, port=port)

    # calculator = FoodNutritionCalculator()
    # # 食物交换份计算
    # # food_exchange = calculator.calc_foodexchange_by_tee(1100)
    # person = Person(Gender.M, "1983-10-30", 170, 60, Pal.LIGHT)
    # food_exchange = calculator.calc_foodexchange(person)
    # print("食物交换份数：")
    # print(food_exchange)
    # print("=" * 200)
    # # 一日食物份数计算
    # foodnum_oneday = calculator.calc_foodnum_oneday(food_exchange, result_structure="bymeal")
    # print("一日食物份数：")
    # print(foodnum_oneday)
    # print("=" * 200)